# -*- coding: utf-8 -*-
"""
UTc! module: src.input.capture

Purpose
=======
 Provides routines for capturing and handling PyGame input.
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv2, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import ConfigParser
import pygame
from pygame.constants import KEYDOWN, KEYUP, JOYAXISMOTION, JOYBUTTONUP, JOYBUTTONDOWN, QUIT, K_BACKSPACE
import random
import re

import src.constants as constants
from src.tankchan import SUBSYSTEM_ENGINES, SUBSYSTEM_SHIELDS, SUBSYSTEM_WEAPONS

_EVENT_READER = None #: A Singleton enforcer: contains a reference to any previously instantiated EventReader.

_BINDING_AXIS_REGEXP = re.compile("axis (\d+)/(positive|negative), (.+)") #: A regular expression used to identify axis bindings.
_BINDING_BUTTON_REGEXP = re.compile("button (\d+), (.+)") #: A regular expression used to identify button bindings.
_BINDING_KEY_REGEXP = re.compile("keycode (\d+) .+") #: A regular expression used to identify keyboard bindings.

_DIGITAL_INTENSITY_BASE = 0.0 #: The base intensity with which new events are added.
_DIGITAL_INTENSITY_SPEEDUP = 1.0 #: The intensity that events gain over the course of a second.

class BindingsHandler(object):
	"""
	Maintains and processes mappings between user input and player actions.
	"""
	_cooldown_flag = False #: True when ignoring toggle events trippable by held buttons.
	_cooldown_time = 0.2 #: The number of seconds to wait before resetting the cooldown flag.
	_cooldown_timer = 0.0 #: The number of seconds remaining before the cooldown flag is reset.
	_joystick_axes = None #: A dictionary mapping joystick movement to player actions.
	_joystick_buttons = None #: A dictionary mapping joystick buttons to player actions.
	_keys = None #: A dictionary mapping keys to player actions.
	_new_bolts = None #: A list of all bolts created during a frame.
	_new_ecms = None #: A list of all ecms created during a frame.
	_new_missiles = None #: A list of all missiles created during a frame.
	_new_target = None #: The newly chosen target, if any.
	_pending_roll = None #: The roll to apply to the player after each frame.
	_pending_pitch = None #: The pitch to apply to the player after each frame.
	_pending_yaw = None #: The yaw to apply to the player after each frame.
	_player = None #: The ship on which this class will operate.
	
	def __init__(self, player, config_file):
		"""
		Loads keybinding data and maps user input to player actions.
		
		@type player: L{src.tankchan.TankChan}
		@param player: The TankChan the player controls.
		@type config_file: basestring
		@param config_file: The path to the keybindings config file.
		
		@raise ValueError: If no keybinding data exists.
		"""
		self._player = player
		self._joystick_axes = {}
		self._joystick_buttons = {}
		
		bindings = KeyConfig()
		if not bindings.read(config_file):
			raise ValueError("No keybinding data found")
			
		joystick_map = {}
		for joystick in [pygame.joystick.Joystick(i) for i in range(pygame.joystick.get_count())]:
			joystick_map[joystick.get_name()] = joystick
			self._joystick_axes[joystick.get_id()] = {}
			self._joystick_buttons[joystick.get_id()] = {}
		self._keys = {}
		
		#Set up maps.
		self._map("movement", "pitch-up", self._movementPitchUp, bindings, joystick_map)
		self._map("movement", "pitch-down", self._movementPitchDown, bindings, joystick_map)
		self._map("movement", "yaw-left", self._movementYawLeft, bindings, joystick_map)
		self._map("movement", "yaw-right", self._movementYawRight, bindings, joystick_map)
		self._map("movement", "roll-left", self._movementRollLeft, bindings, joystick_map)
		self._map("movement", "roll-right", self._movementRollRight, bindings, joystick_map)
		self._map("movement", "speed-up", self._movementSpeedUp, bindings, joystick_map)
		self._map("movement", "speed-down", self._movementSpeedDown, bindings, joystick_map)
		
		self._map("weapons", "fire-bolt", self._weaponsFirePrimary, bindings, joystick_map)
		self._map("weapons", "next-bolt", self._weaponsNextPrimary, bindings, joystick_map)
		self._map("weapons", "prev-bolt", self._weaponsPreviousPrimary, bindings, joystick_map)
		self._map("weapons", "fire-missile", self._weaponsFireSecondary, bindings, joystick_map)
		self._map("weapons", "next-missile", self._weaponsNextSecondary, bindings, joystick_map)
		self._map("weapons", "prev-missile", self._weaponsPreviousSecondary, bindings, joystick_map)
		self._map("weapons", "fire-ecm", self._weaponsFireCountermeasure, bindings, joystick_map)
		
		self._map("reactor", "up-engines", self._reactorEnginesUp, bindings, joystick_map)
		self._map("reactor", "down-engines", self._reactorEnginesDown, bindings, joystick_map)
		self._map("reactor", "up-shields", self._reactorShieldsUp, bindings, joystick_map)
		self._map("reactor", "down-shields", self._reactorShieldsDown, bindings, joystick_map)
		self._map("reactor", "up-weapons", self._reactorWeaponsUp, bindings, joystick_map)
		self._map("reactor", "down-weapons", self._reactorWeaponsDown, bindings, joystick_map)
		
		self._map("targeting", "closest", self._targetClosest, bindings, joystick_map)
		self._map("targeting", "random", self._targetRandom, bindings, joystick_map)
		self._map("targeting", "reticle", self._targetReticle, bindings, joystick_map)
		
	def processEvents(self, axes, buttons, keys, seconds, target, other_players, camera):
		"""
		Processes all user input, updating the game environment as necessary.
		
		@type axes: dict
		@param axes: A dictionary of joystick axis activation values, as returned
		    by L{EventReader.getEvents}.
		@type buttons: dict
		@param buttons: A dictionary of joystick button values, as returned by
		    L{EventReader.getEvents}.
		@type keys: list
		@param keys: A list of all keys to be processed, as returned by
		    L{EventReader.getEvents}.
		@type seconds: float
		@param seconds: The amount of time over which these events were active.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's current target, if any.
		@type other_players: sequence
		@param other_players: A collection of all non-player L{tankchan.TankChan}
		    objects found in the game.
		@type camera: L{src.camera.Camera}
		@param camera: The camera used to render the player's view of the game.
		
		@rtype: tuple(4)
		@return: A list of all bolts created during this event, a list of all
		    missiles created during this event, a list of all ECMs created during
		    this event, and the player's new target.
		"""
		#Set values to their initial state.
		self._new_bolts = []
		self._new_ecms = []
		self._new_missiles = []
		self._new_target = None
		self._pending_roll = 0.0
		self._pending_pitch = 0.0
		self._pending_yaw = 0.0
		
		#Re-enable time-sensitive events when ready.
		if self._cooldown_flag:
			self._cooldown_timer -= seconds
			if self._cooldown_timer <= 0.0:
				self._cooldown_flag = False
				
		#Traverse the maps and activate functions.
		for (id, axes) in axes.iteritems():
			if axes:
				mapping = self._joystick_axes[id]
				for (axis, value) in axes:
					function = mapping.get((axis, value >= 0.0))
					if function:
						function(abs(value), seconds, target, other_players)
						
		for (id, buttons) in buttons.iteritems():
			if buttons:
				mapping = self._joystick_buttons[id]
				for (button, intensity) in buttons:
					function = mapping.get(button)
					if function:
						function(intensity, seconds, target, other_players)
						
		for (key, ucode, intensity) in keys:
			function = self._keys.get(key)
			if function:
				function(intensity, seconds, target, other_players)
				
		#Update the camera's orientation.
		if self._pending_pitch or self._pending_yaw or self._pending_roll:
			camera.rotate(*self._player.applyRotation(self._pending_pitch, self._pending_yaw, self._pending_roll))
			
		return (self._new_bolts, self._new_missiles, self._new_ecms, self._new_target or target)
		
	def _map(self, section, option, function, bindings, joystick_map):
		"""
		Maps an input event type to a function.
		
		@type section: str
		@param section: The section of the config file from which the function's
		    input trigger will be read.
		@type option: str
		@param option: The option of the config file from which the function's
		    input trigger will be read.
		@type function: callable
		@param function: The function to be associated with the config file's
		    input event.
		@type bindings: L{KeyConfig}
		@param bindings: The config file's broker.
		@type joystick_map: dict
		@param joystick_map: A mapping between joystick names and device IDs.
		
		@raise ValueError: If a required joystick is missing or the config file is
		    corrupt.
		"""
		binding = bindings.get(section, option)
		if binding:
			axis_match = _BINDING_AXIS_REGEXP.match(binding)
			if axis_match:
				joystick = joystick_map.get(axis_match.group(3))
				if not joystick:
					raise ValueError("Required joystick, '%s', not found" % (axis_match.group(3)))
				joystick.init()
				self._joystick_axes[joystick.get_id()][(int(axis_match.group(1)), axis_match.group(2) == 'positive')] = function
			else:
				button_match = _BINDING_BUTTON_REGEXP.match(binding)
				if button_match:
					joystick = joystick_map.get(button_match.group(2))
					if not joystick:
						raise ValueError("Required joystick, '%s', not found" % (button_match.group(2)))
					joystick.init()
					self._joystick_buttons[joystick.get_id()][int(button_match.group(1))] = function
				else:
					key_match = _BINDING_KEY_REGEXP.match(binding)
					if key_match:
						self._keys[int(key_match.group(1))] = function
					else:
						raise ValueError("Unable to parse binding: %s" % (binding))
						
	def _movementPitchUp(self, intensity, seconds, target, other_players):
		"""
		Tilts the player's ship's nose up.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_pitch -= intensity * seconds
		
	def _movementPitchDown(self, intensity, seconds, target, other_players):
		"""
		Tilts the player's ship's nose down.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_pitch += intensity * seconds
		
	def _movementYawLeft(self, intensity, seconds, target, other_players):
		"""
		Twists the player's ship's nose left.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_yaw += intensity * seconds
		
	def _movementYawRight(self, intensity, seconds, target, other_players):
		"""
		Twists the player's ship's nose right.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_yaw -= intensity * seconds
		
	def _movementRollLeft(self, intensity, seconds, target, other_players):
		"""
		Rolls the player's ship counter-clockwise.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_roll -= intensity * seconds
		
	def _movementRollRight(self, intensity, seconds, target, other_players):
		"""
		Rolls the player's ship clockwise.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._pending_roll += intensity * seconds
		
	def _movementSpeedDown(self, intensity, seconds, target, other_players):
		"""
		Lowers the player's target speed.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.speedDown(intensity * seconds)
		
	def _movementSpeedUp(self, intensity, seconds, target, other_players):
		"""
		Raises the player's target speed.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.speedUp(intensity * seconds)
		
	def _weaponsFirePrimary(self, intensity, seconds, target, other_players):
		"""
		Fires the player's primary weapons.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._new_bolts += self._player.dischargeBolts(target)
		
	def _weaponsNextPrimary(self, intensity, seconds, target, other_players):
		"""
		Selects the player's next primary weapons bank.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			self._player.getWeaponSystems()[0].cycleNext()
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _weaponsPreviousPrimary(self, intensity, seconds, target, other_players):
		"""
		Selects the player's previous primary weapons bank.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			self._player.getWeaponSystems()[0].cyclePrevious()
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _weaponsFireSecondary(self, intensity, seconds, target, other_players):
		"""
		Fires the player's secondary weapons.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._new_missiles += self._player.dischargeMissile(target)
		
	def _weaponsNextSecondary(self, intensity, seconds, target, other_players):
		"""
		Selects the player's next secondary weapons bank.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			self._player.getWeaponSystems()[1].cycleNext()
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _weaponsPreviousSecondary(self, intensity, seconds, target, other_players):
		"""
		Selects the player's previous secondary weapons bank.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			self._player.getWeaponSystems()[1].cyclePrevious()
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _weaponsFireCountermeasure(self, intensity, seconds, target, other_players):
		"""
		Fires one of the player's ECMs.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			self._new_ecms += self._player.dischargeECM()
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _reactorEnginesUp(self, intensity, seconds, target, other_players):
		"""
		Increases the player's reactor output allocation to engines.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorUp(SUBSYSTEM_ENGINES, intensity * seconds)
		
	def _reactorEnginesDown(self, intensity, seconds, target, other_players):
		"""
		Decreases the player's reactor output allocation to engines.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorDown(SUBSYSTEM_ENGINES, intensity * seconds)
		
	def _reactorShieldsUp(self, intensity, seconds, target, other_players):
		"""
		Increases the player's reactor output allocation to shields.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorUp(SUBSYSTEM_SHIELDS, intensity * seconds)
		
	def _reactorShieldsDown(self, intensity, seconds, target, other_players):
		"""
		Decreases the player's reactor output allocation to shields.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorDown(SUBSYSTEM_SHIELDS, intensity * seconds)
		
	def _reactorWeaponsUp(self, intensity, seconds, target, other_players):
		"""
		Increases the player's reactor output allocation to weapons.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorUp(SUBSYSTEM_WEAPONS, intensity * seconds)
		
	def _reactorWeaponsDown(self, intensity, seconds, target, other_players):
		"""
		Decreases the player's reactor output allocation to weapons.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		self._player.reactorDown(SUBSYSTEM_WEAPONS, intensity * seconds)
		
	def _targetClosest(self, intensity, seconds, target, other_players):
		"""
		Targets the closest hostile player, relative to the player's position.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			player_position = self._player.getPosition()
			hostiles_by_proximity = sorted([(other_player.getDistance(player_position), other_player) for other_player in other_players if other_player.isAlive() and other_player.isHostile(self._player)])
			if hostiles_by_proximity:
				self._new_target = hostiles_by_proximity[0][1]
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _targetRandom(self, intensity, seconds, target, other_players):
		"""
		Targets a random hostile player.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			hostiles = [other_player for other_player in other_players if other_player.isAlive() and other_player.isHostile(self._player)]
			if hostiles:
				self._new_target = random.choice(hostiles)
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
	def _targetReticle(self, intensity, seconds, target, other_players):
		"""
		Targets the closest player under the player's reticle.
		
		@type intensity: float
		@param intensity: The intensity of the event.
		@type seconds: float
		@param seconds: The duration of the event.
		@type target: L{tankchan.TankChan}|None
		@param target: The player's target, if any.
		@type other_players: sequence
		@param other_players: A list of all non-player L{tankchan.TankChan}
		    objects.
		"""
		if not self._cooldown_flag:
			player_position = self._player.getPosition()
			players_under_reticle = sorted([(other_player.getDistance(player_position), other_player) for other_player in other_players if other_player.isAlive() and other_player.isUnderReticle()])
			if players_under_reticle:
				self._new_target = players_under_reticle[0][1]
			self._cooldown_timer = self._cooldown_time
			self._cooldown_flag = True
			
			
class EventReader(object):
	"""
	A handler for reading PyGame events in an intuitive manner.
	"""
	_joystick_axes_positions = {} #: A collection of all current joystick axis values, if non-neutral.
	_joystick_buttons_depressed = {} #: A collection of all currently pressed joystick buttons.
	_keys_depressed = [] #: A collection of all currently pressed keys.
	
	def __init__(self):
		"""
		Initialises the EventReader, preparing it to receive events from joysticks
		and the keyboard, and disabling all unwanted event types.
		"""
		if _EVENT_READER: #Go Singleton.
			self = _EVENT_READER
		else: #Initialise variables and the event queue.
			#Disable unhandleable events.
			pygame.event.set_blocked(pygame.constants.ACTIVEEVENT)
			pygame.event.set_blocked(pygame.constants.MOUSEMOTION)
			pygame.event.set_blocked(pygame.constants.MOUSEBUTTONDOWN)
			pygame.event.set_blocked(pygame.constants.MOUSEBUTTONUP)
			pygame.event.set_blocked(pygame.constants.JOYBALLMOTION)
			pygame.event.set_blocked(pygame.constants.JOYHATMOTION)
			pygame.event.set_blocked(pygame.constants.VIDEORESIZE)
			pygame.event.set_blocked(pygame.constants.VIDEOEXPOSE)
			pygame.event.set_blocked(pygame.constants.USEREVENT)
			
			#Initialise joystick event variables.
			for i in range(pygame.joystick.get_count()):
				self._joystick_axes_positions[i] = []
				self._joystick_buttons_depressed[i] = []
				
			pygame.event.get() #Clear the event queue.
			
	def clearPositions(self):
		"""
		Clears all joystick axis values, joystick button values, and keyboard key
		values.
		"""
		for joystick in self._joystick_axes_positions:
			self._joystick_axes_positions[joystick] = {}
		for joystick in self._joystick_buttons_depressed:
			self._joystick_buttons_depressed[joystick] = []
		self._keys_depressed = []
		
	def getEvents(self, seconds=None):
		"""
		Returns all events that may need to be handled. This includes keys,
		buttons, and axes that have remained unchanged, though active, since the
		last call.
		
		@type seconds: float|None
		@param seconds: The number of seconds over which to boost intensity, or
		    None if intensity doesn't matter.
		
		@rtype: tuple(4)
		@return: Four distinct collections of elements::
		    - A dictionary of all active joystick axes:
		        - A dictionary keyed by joystick ID, with values being another
		            dictionary keyed by axis number, with values equal to the
		            axis's offset.
		    - A dictionary of all active joystick buttons:
		        - A dictionary keyed by joystick ID, with values being a list of
		            active (button IDs, intensities).
		    - A list of all pressed keys, as tuples of
		        (keyscan_code, unicode_value|None, intensity)
		    - A list of all keys newly pressed during this cycle, as tuples of
		        (keyscan_code, unicode_value|None)
		
		@raise L{UserQuit}: If the user triggers a QUIT event.
		"""
		new_keys = []
		for e in pygame.event.get():
			if e.type == QUIT:
				raise UserQuit("User generated quit event")
			elif e.type == KEYDOWN:
				ucode = None
				if (e.key == K_BACKSPACE or 32 <= e.key <= 127):
					ucode = e.unicode
				new_keys.append((e.key, ucode))
				if not [None for key in self._keys_depressed if key[0] == e.key]:
					self._keys_depressed.append((e.key, ucode, _DIGITAL_INTENSITY_BASE))
			elif e.type == KEYUP:
				self._keys_depressed = [key for key in self._keys_depressed if not key[0] == e.key]
			elif e.type == JOYBUTTONDOWN:
				depressed_buttons = self._joystick_buttons_depressed[e.joy]
				if not [None for button in depressed_buttons if button[0] == e.button]:
					depressed_buttons.append((e.button, _DIGITAL_INTENSITY_BASE))
			elif e.type == JOYBUTTONUP:
				self._joystick_buttons_depressed[e.joy] = [button for button in self._joystick_buttons_depressed[e.joy] if not button[0] == e.button]
			elif e.type == JOYAXISMOTION:
				axes_positions = [value for value in self._joystick_axes_positions[e.joy] if not value[0] == e.axis]
				if abs(e.value) >= 0.025:
					axes_positions.append((e.axis, e.value))
				self._joystick_axes_positions[e.joy] = axes_positions
				
		if seconds: #Boost intensity of buttons and keys held down over time.
			intensity_gain = _DIGITAL_INTENSITY_SPEEDUP * seconds
			self._keys_depressed = [(key, ucode, min(1.0, intensity + intensity_gain)) for (key, ucode, intensity) in self._keys_depressed]
			for joystick in self._joystick_buttons_depressed:
				self._joystick_buttons_depressed[joystick] = [(button, min(1.0, intensity + intensity_gain)) for (button, intensity) in self._joystick_buttons_depressed[joystick]]
				
		#Yes, these should be copied, but that takes time, and this needs to be fast.
		return (self._joystick_axes_positions, self._joystick_buttons_depressed, self._keys_depressed, new_keys)
		
		
class KeyConfig(ConfigParser.RawConfigParser):
	"""
	A convenience wrapper around RawConfigParser that handles missing values.
	"""
	def get(self, section, option):
		"""
		Returns the requested value or None if the value does not exist.
		
		@type section: str
		@param section: The section from which the value should be retrieved.
		@type option: str
		@param option: The option from which the value should be retrieved.
		
		@rtype: str|None
		@return: The requested value or None if the option does not exist.
		"""
		try:
			return ConfigParser.RawConfigParser.get(self, section, option)
		except ConfigParser.NoOptionError:
			return None
			
	def set(self, section, option, value):
		"""
		Sets the specified value, or removes the option if the value is None.
		
		@type section: str
		@param section: The section to which the value should be set.
		@type option: str
		@param option: The option to which the value should be set.
		@type value: str|None
		@param value: The value to set, or None if the option should be removed.
		"""
		if not value is None:
			ConfigParser.RawConfigParser.set(self, section, option, value)
		else:
			self.remove_option(section, option)
			
			
class Error(Exception):
	"""
	The base class from which all exceptions native to this module inherit.
	"""
	def __init__(self, message):
		"""
		Creates a generic Error instance.
		
		@type message: basestring
		@param message: The message to be relayed.
		"""
		Exception.__init__(self, message)
		
class UserQuit(Error):
	"""
	An exception that signifies that the user manually quit the game. 
	"""
	pass
	